/* Copyright (c) 2008, John Cronin
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the copyright holder nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */

/* a simple memory manager */

#include "memmgr.h"
#include "console.h"
#include "vmem.h"
#include "stdlib.h"
#include "malloc.h"
#include "mboot.h"
#include "kif.h"

unsigned long int bitmap[0x8000];		/* enough for 1 bit per 4kb page == 32 pages */

struct linked_mmap *first = NULL;
struct linked_mmap *last = NULL;

/* each dword contains 32 bits, so can reference 128kB of memory (=0x20000 bytes) */

#define PAGE_INDEX(x)		((x) >> 12)

#define DWORD_INDEX(x)		(PAGE_INDEX(x) >> 5)
#define BIT_INDEX(x)		(PAGE_INDEX(x) & 0x1f)

void _set(unsigned long int addr);
void _unset(unsigned long int addr);
int _isset(unsigned long int addr);
void *_malloc_fixed(unsigned long int addr, unsigned long int length, int type, int add_list);

int is_free(unsigned long int addr, unsigned long int length)
{
	unsigned long int isfree = 1;
	unsigned long int block_offset;

	for(block_offset = 0; block_offset < length; block_offset += PAGESIZE) {
		if(_isset(addr + block_offset)) {
			isfree = 0;
			break;
		}
	}

	return isfree;
}

void *malloc_fixed(unsigned long int addr, unsigned long int length, int type, int add_list)
{
	if(!is_free(addr, length))
		return NULL;

	return _malloc_fixed(addr, length, type, add_list);
}

void *_malloc_fixed(unsigned long int addr, unsigned long int length, int type, int add_list)
{
	unsigned long int cur_set_page;

	for(cur_set_page = addr; cur_set_page < (addr + length); cur_set_page += PAGESIZE)
		_set(cur_set_page);
	if(add_list)
		add_linked_list((unsigned long long int)addr, (unsigned long long int)length, type);
	return (void *)addr;
}

void *malloc_align(unsigned long int length, unsigned int align, int type, int add_list)
{
	unsigned long int test = 0;
	unsigned long int found = 0;

	if((length % PAGESIZE) || (align % PAGESIZE)) {
		puts("Error: can only process multiple of page sized and aligned requests");
		return NULL;
	}

	while(1) {
		if(is_free(test, length)) {
			found = 1;
			break;
		}

		if(test >= (unsigned long int)(-align))
			break;

		test += align;
	}

	if(found)
		return _malloc_fixed(test, length, type, add_list);

	return NULL;
}

void freepage(void *p)
{
	_unset((unsigned long int)p);
}

unsigned long long int mark_used(unsigned long long int start, unsigned long long int length)
{
	unsigned long long int cur;

	for(cur = start; cur < (start + length); cur += PAGESIZE)
		_set((unsigned long int)cur);
	return start;
}

unsigned long long int mark_free(unsigned long long int start, unsigned long long int length)
{
	unsigned long long int cur;

	for(cur = start; cur < (start + length); cur += PAGESIZE)
		_unset((unsigned long int)cur);
	return start;
}

void _set(unsigned long addr)
{
	unsigned long int setval;

	setval = 1 << BIT_INDEX(addr);
	bitmap[DWORD_INDEX(addr)] |= setval;
}

void _unset(unsigned long addr)
{
	unsigned long int setval;

	setval = 1 << BIT_INDEX(addr);
	bitmap[DWORD_INDEX(addr)] &= (~setval);
}

int _isset(unsigned long addr) {
	return (bitmap[DWORD_INDEX(addr)] >> BIT_INDEX(addr)) & 0x1;
}

void add_linked_list(unsigned long long int addr, unsigned long long int length, int type)
{
	struct linked_mmap *new_mmap = (struct linked_mmap *)malloc(sizeof(struct linked_mmap));

	new_mmap->mmap.base_addr = addr;
	new_mmap->mmap.length = length;
	new_mmap->mmap.type = type;

#ifdef _DEBUG
	if(new_mmap->mmap.type == 1) {
		_puts("Adding free: ", 0);
		puthex(new_mmap->mmap.base_addr);
		_puts("-", 0);
		puthex(new_mmap->mmap.base_addr + new_mmap->mmap.length);
		_puts("", 1);
	}
#endif

	new_mmap->next = NULL;
	if(first == NULL)
		first = new_mmap;

	if(last == NULL)
		last = new_mmap;
	else {
		last->next = new_mmap;
		last = new_mmap;
	}
}

int count_linked_list()
{
	int i = 0;

	struct linked_mmap *cur = first;

	while(cur != NULL) {
		cur = cur->next;
		i++;
	}

	return i;
}

void memmgr_init_linked_list()
{
	struct linked_mmap *cur_mmap = first;

	while(cur_mmap != NULL)
	{
		Init_Multiboot_MemoryMap(&cur_mmap->mmap);
		cur_mmap = cur_mmap->next;
	}
}
